
#include "3dLight.h"


int C3dLight::Init(int			_id,
				   int          _state,      // state of light
				   int          _attr,       // type of light, and extra qualifiers
				   RGBAV1       _c_ambient,  // ambient light intensity
				   RGBAV1       _c_diffuse,  // diffuse light intensity
				   RGBAV1       _c_specular, // specular light intensity
				   CPoint4d&	_pos,        // position of light
				   CVector4d&	_dir,        // direction of light
				   float        _kc,         // attenuation factors
				   float        _kl, 
				   float        _kq,  
				   float        _spot_inner, // inner angle for spot light
				   float        _spot_outer, // outer angle for spot light
				   float        _pf)         // power factor/falloff for spot lights
{
	// this function initializes a light based on the flags sent in _attr, values that
	// aren't needed are set to 0 by caller

	// all good, initialize the light (many fields may be dead)
	state       = _state;      // state of light
	id          = _id;       // id of light
	attr        = _attr;       // type of light, and extra qualifiers

	c_ambient   = _c_ambient;  // ambient light intensity
	c_diffuse   = _c_diffuse;  // diffuse light intensity
	c_specular  = _c_specular; // specular light intensity

	kc          = _kc;         // constant, linear, and quadratic attenuation factors
	kl          = _kl;   
	kq          = _kq;   

	pos = _pos;					// position of light

	dir = _dir;					// direction of light
	// normalize it
	dir.Normalize();

	spot_inner  = _spot_inner; // inner angle for spot light
	spot_outer  = _spot_outer; // outer angle for spot light
	pf          = _pf;         // power factor/falloff for spot lights

	// return light index as success
	return 0;

} // end Create_Light_LIGHTV1

int C3dLight::Reset()
{
	state		= 0; // state of light
	id			= 0;    // id of light
	attr		= 0;  // type of light, and extra qualifiers

	memset(&c_ambient, 0, sizeof(c_ambient));		// ambient light intensity
	memset(&c_diffuse, 0, sizeof(c_diffuse));		// diffuse light intensity
	memset(&c_specular, 0, sizeof(c_specular));		// specular light intensity

	kc			= 0;
	kl			= 0;
	kq			= 0;		// attenuation factors
	spot_inner	= 0;		// inner angle for spot light
	spot_outer	= 0;		// outer angle for spot light
	pf			= 0;		// power factor/falloff for spot lights

	iaux1		= 0;
	iaux2		= 0;	// auxiliary vars for future expansion
	faux1		= 0;
	faux2		= 0;
	ptr			= NULL;

	// return success
	return 0;

} // end Reset

int C3dLightMgr::Light_Obj4d_World16(	CObj4d& obj,			// object to process
										int max_lights)			// maximum lights in list
{
	// 16-bit version of function
	// function lights an object based on the sent lights and camera. the function supports
	// constant/pure shading (emmisive), flat shading with ambient, infinite, point lights, and spot lights
	// note that this lighting function is rather brute force and simply follows the math, however
	// there are some clever integer operations that are used in scale 256 rather than going to floating
	// point, but why? floating point and ints are the same speed, HOWEVER, the conversion to and from floating
	// point can be cycle intensive, so if you can keep your calcs in ints then you can gain some speed
	// also note, type 1 spot lights are simply point lights with direction, the "cone" is more of a function
	// of the falloff due to attenuation, but they still look like spot lights
	// type 2 spot lights are implemented with the intensity having a dot product relationship with the
	// angle from the surface point to the light direction just like in the optimized model, but the pf term
	// that is used for a concentration control must be 1,2,3,.... integral and non-fractional


	unsigned int r_base, g_base, b_base,  // base color being lit
		r_sum,  g_sum,  b_sum,   // sum of lighting process over all lights
		shaded_color;            // final color

	float dp,     // dot product 
		dist,   // distance from light to surface
		i,      // general intensities
		nl,     // length of normal
		atten;  // attenuation computations

	// test if the object is culled
	if (!(obj.state & OBJECT4DV1_STATE_ACTIVE) ||
		(obj.state & OBJECT4DV1_STATE_CULLED) ||
		!(obj.state & OBJECT4DV1_STATE_VISIBLE))
		return(0); 

	// process each poly in mesh
	for (int poly=0; poly < obj.num_polys; poly++)
	{
		// acquire polygon
		POLY4DV1_PTR curr_poly = &obj.plist[poly];

		// is this polygon valid?
		// test this polygon if and only if it's not clipped, not culled,
		// active, and visible. Note we test for backface in the event that
		// a previous call might have already determined this, so why work
		// harder!
		if (!(curr_poly->state & POLY4DV1_STATE_ACTIVE) ||
			(curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||
			(curr_poly->state & POLY4DV1_STATE_BACKFACE) )
			continue; // move onto next poly

		// extract vertex indices into master list, rember the polygons are 
		// NOT self contained, but based on the vertex list stored in the object
		// itself
		int vindex_0 = curr_poly->vert[0];
		int vindex_1 = curr_poly->vert[1];
		int vindex_2 = curr_poly->vert[2];

		// we will use the transformed polygon vertex list since the backface removal
		// only makes sense at the world coord stage further of the pipeline 

		// test the lighting mode of the polygon (use flat for flat, gouraud))
		if (curr_poly->attr & POLY4DV1_ATTR_SHADE_MODE_FLAT || curr_poly->attr & POLY4DV1_ATTR_SHADE_MODE_GOURAUD)
		{
			// step 1: extract the base color out in RGB mode
			if (m_pDxDrawEnv->m_dd_pixel_format == DD_PIXEL_FORMAT565)
			{
				_RGB565FROM16BIT(curr_poly->color, &r_base, &g_base, &b_base);

				// scale to 8 bit 
				r_base <<= 3;
				g_base <<= 2;
				b_base <<= 3;
			} // end if
			else
			{
				_RGB555FROM16BIT(curr_poly->color, &r_base, &g_base, &b_base);

				// scale to 8 bit 
				r_base <<= 3;
				g_base <<= 3;
				b_base <<= 3;
			} // end if

			// initialize color sum
			r_sum  = 0;
			g_sum  = 0;
			b_sum  = 0;

			// loop thru lights
			for (int curr_light = 0; curr_light < max_lights; curr_light++)
			{
				// is this light active
				if (m_lights[curr_light].state==LIGHTV1_STATE_OFF)
					continue;

				// what kind of light are we dealing with
				if (m_lights[curr_light].attr & LIGHTV1_ATTR_AMBIENT)
				{
					// simply multiply each channel against the color of the 
					// polygon then divide by 256 to scale back to 0..255
					// use a shift in real life!!! >> 8
					r_sum+= ((m_lights[curr_light].c_ambient.r * r_base) / 256);
					g_sum+= ((m_lights[curr_light].c_ambient.g * g_base) / 256);
					b_sum+= ((m_lights[curr_light].c_ambient.b * b_base) / 256);

					// there better only be one ambient light!

				} // end if
				else
					if (m_lights[curr_light].attr & LIGHTV1_ATTR_INFINITE)
					{
						// infinite lighting, we need the surface normal, and the direction
						// of the light source

						// we need to compute the normal of this polygon face, and recall
						// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
						CVector4d	n;

						nl = Calc_Plane_n(obj.vlist_trans[ vindex_0 ], obj.vlist_trans[ vindex_1 ], obj.vlist_trans[ vindex_2 ], n);


						// ok, recalling the lighting model for infinite lights
						// I(d)dir = I0dir * Cldir
						// and for the diffuse model
						// Itotald =   Rsdiffuse*Idiffuse * (n . l)
						// so we basically need to multiple it all together
						// notice the scaling by 128, I want to avoid floating point calculations, not because they 
						// are slower, but the conversion to and from cost cycles
						dp = n.Dot(m_lights[curr_light].dir);

						// only add light if dp > 0
						if (dp > 0)
						{ 
							i = 128*dp/nl; 
							r_sum+= (m_lights[curr_light].c_diffuse.r * r_base * i) / (256*128);
							g_sum+= (m_lights[curr_light].c_diffuse.g * g_base * i) / (256*128);
							b_sum+= (m_lights[curr_light].c_diffuse.b * b_base * i) / (256*128);
						} // end if

					} // end if infinite light
					else
						if (m_lights[curr_light].attr & LIGHTV1_ATTR_POINT)
						{
							// perform point light computations
							// light model for point light is once again:
							//              I0point * Clpoint
							//  I(d)point = ___________________
							//              kc +  kl*d + kq*d2              
							//
							//  Where d = |p - s|
							// thus it's almost identical to the infinite light, but attenuates as a function
							// of distance from the point source to the surface point being lit

							// we need to compute the normal of this polygon face, and recall
							// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
							CVector4d	n, l;

							nl = Calc_Plane_n(obj.vlist_trans[ vindex_0 ], obj.vlist_trans[ vindex_1 ], obj.vlist_trans[ vindex_2 ], n);

							// compute vector from surface to light
							l.Build(obj.vlist_trans[ vindex_0 ], m_lights[curr_light].pos);
							// compute distance and attenuation
							dist = l.Fast_Length();


							// and for the diffuse model
							// Itotald =   Rsdiffuse*Idiffuse * (n . l)
							// so we basically need to multiple it all together
							// notice the scaling by 128, I want to avoid floating point calculations, not because they 
							// are slower, but the conversion to and from cost cycles
							dp = n.Dot(l);

							// only add light if dp > 0
							if (dp > 0)
							{ 
								atten =  (m_lights[curr_light].kc + m_lights[curr_light].kl*dist + m_lights[curr_light].kq*dist*dist);    

								i = 128*dp / (nl * dist * atten ); 

								r_sum += (m_lights[curr_light].c_diffuse.r * r_base * i) / (256*128);
								g_sum += (m_lights[curr_light].c_diffuse.g * g_base * i) / (256*128);
								b_sum += (m_lights[curr_light].c_diffuse.b * b_base * i) / (256*128);
							} // end if

						} // end if point
						else
							if (m_lights[curr_light].attr & LIGHTV1_ATTR_SPOTLIGHT1)
							{
								// perform spotlight/point computations simplified model that uses
								// point light WITH a direction to simulate a spotlight
								// light model for point light is once again:
								//              I0point * Clpoint
								//  I(d)point = ___________________
								//              kc +  kl*d + kq*d2              
								//
								//  Where d = |p - s|
								// thus it's almost identical to the infinite light, but attenuates as a function
								// of distance from the point source to the surface point being lit

								// we need to compute the normal of this polygon face, and recall
								// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
								CVector4d	n, l;

								nl = Calc_Plane_n(obj.vlist_trans[ vindex_0 ], obj.vlist_trans[ vindex_1 ], obj.vlist_trans[ vindex_2 ], n);

								// compute vector from surface to light
								l.Build(obj.vlist_trans[ vindex_0 ], m_lights[curr_light].pos);
								// compute distance and attenuation
								dist = l.Fast_Length();

								// and for the diffuse model
								// Itotald =   Rsdiffuse*Idiffuse * (n . l)
								// so we basically need to multiple it all together
								// notice the scaling by 128, I want to avoid floating point calculations, not because they 
								// are slower, but the conversion to and from cost cycles

								// note that I use the direction of the light here rather than a the vector to the light
								// thus we are taking orientation into account which is similar to the spotlight model
								dp = n.Dot(m_lights[curr_light].dir);

								// only add light if dp > 0
								if (dp > 0)
								{ 
									atten =  (m_lights[curr_light].kc + m_lights[curr_light].kl*dist + m_lights[curr_light].kq*dist*dist);    

									i = 128*dp / (nl * atten ); 

									r_sum += (m_lights[curr_light].c_diffuse.r * r_base * i) / (256*128);
									g_sum += (m_lights[curr_light].c_diffuse.g * g_base * i) / (256*128);
									b_sum += (m_lights[curr_light].c_diffuse.b * b_base * i) / (256*128);
								} // end if

							} // end if spotlight1
							else
								if (m_lights[curr_light].attr & LIGHTV1_ATTR_SPOTLIGHT2) // simple version
								{
									// perform spot light computations
									// light model for spot light simple version is once again:
									//         	     I0spotlight * Clspotlight * MAX( (l . s), 0)^pf                     
									// I(d)spotlight = __________________________________________      
									//               		 kc + kl*d + kq*d2        
									// Where d = |p - s|, and pf = power factor

									// thus it's almost identical to the point, but has the extra term in the numerator
									// relating the angle between the light source and the point on the surface

									// we need to compute the normal of this polygon face, and recall
									// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
									CVector4d	n, d, s;

									nl = Calc_Plane_n(obj.vlist_trans[ vindex_0 ], obj.vlist_trans[ vindex_1 ], obj.vlist_trans[ vindex_2 ], n);

									// and for the diffuse model
									// Itotald =   Rsdiffuse*Idiffuse * (n . l)
									// so we basically need to multiple it all together
									// notice the scaling by 128, I want to avoid floating point calculations, not because they 
									// are slower, but the conversion to and from cost cycles
									dp = n.Dot(m_lights[curr_light].dir);

									// only add light if dp > 0
									if (dp > 0)
									{ 
										// compute vector from light to surface (different from l which IS the light dir)
										s.Build(m_lights[curr_light].pos, obj.vlist_trans[ vindex_0 ]);

										// compute length of s (distance to light source) to normalize s for lighting calc
										dist = s.Fast_Length();

										// compute spot light term (s . l)
										float dpsl = s.Dot(m_lights[curr_light].dir)/dist;

										// proceed only if term is positive
										if (dpsl > 0) 
										{
											// compute attenuation
											atten = (m_lights[curr_light].kc + m_lights[curr_light].kl*dist + m_lights[curr_light].kq*dist*dist);    

											// for speed reasons, pf exponents that are less that 1.0 are out of the question, and exponents
											// must be integral
											float dpsl_exp = dpsl;

											// exponentiate for positive integral powers
											for (int e_index = 1; e_index < (int)m_lights[curr_light].pf; e_index++)
												dpsl_exp*=dpsl;

											// now dpsl_exp holds (dpsl)^pf power which is of course (s . l)^pf 

											i = 128*dp * dpsl_exp / (nl * atten ); 

											r_sum += (m_lights[curr_light].c_diffuse.r * r_base * i) / (256*128);
											g_sum += (m_lights[curr_light].c_diffuse.g * g_base * i) / (256*128);
											b_sum += (m_lights[curr_light].c_diffuse.b * b_base * i) / (256*128);

										} // end if

									} // end if

								} // end if spot light

			} // end for light

			// make sure colors aren't out of range
			if (r_sum  > 255) r_sum = 255;
			if (g_sum  > 255) g_sum = 255;
			if (b_sum  > 255) b_sum = 255;

			// write the color
			shaded_color = RGB16Bit(r_sum, g_sum, b_sum);
			curr_poly->color = (int)((shaded_color << 16) | curr_poly->color);

		} // end if
		else // assume POLY4DV1_ATTR_SHADE_MODE_CONSTANT
		{
			// emmisive shading only, copy base color into upper 16-bits
			// without any change
			curr_poly->color = (int)((curr_poly->color << 16) | curr_poly->color);
		} // end if

	} // end for poly

	// return success
	return(1);

} // end Light_Obj4d_World16

int C3dLightMgr::Light_RenderList_World16(	CRenderList& rend_list,			// object to process
											int max_lights)					// maximum lights in list
{
		// 16-bit version of function
	// function lights the enture rendering list based on the sent lights and camera. the function supports
	// constant/pure shading (emmisive), flat shading with ambient, infinite, point lights, and spot lights
	// note that this lighting function is rather brute force and simply follows the math, however
	// there are some clever integer operations that are used in scale 256 rather than going to floating
	// point, but why? floating point and ints are the same speed, HOWEVER, the conversion to and from floating
	// point can be cycle intensive, so if you can keep your calcs in ints then you can gain some speed
	// also note, type 1 spot lights are simply point lights with direction, the "cone" is more of a function
	// of the falloff due to attenuation, but they still look like spot lights
	// type 2 spot lights are implemented with the intensity having a dot product relationship with the
	// angle from the surface point to the light direction just like in the optimized model, but the pf term
	// that is used for a concentration control must be 1,2,3,.... integral and non-fractional

	// also note since we are dealing with a rendering list and not object, the final lit color is
	// immediately written over the real color

	unsigned int r_base, g_base, b_base,  // base color being lit
		r_sum,  g_sum,  b_sum,   // sum of lighting process over all lights
		shaded_color;            // final color

	float dp,     // dot product 
		dist,   // distance from light to surface
		i,      // general intensities
		nl,     // length of normal
		atten;  // attenuation computations


	// for each valid poly, light it...
	for (int poly=0; poly < rend_list.num_polys; poly++)
	{
		// acquire polygon
		POLYF4DV1_PTR curr_poly = rend_list.poly_ptrs[poly];

		// light this polygon if and only if it's not clipped, not culled,
		// active, and visible
		if (!(curr_poly->state & POLY4DV1_STATE_ACTIVE) ||
			(curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||
			(curr_poly->state & POLY4DV1_STATE_BACKFACE) )
			continue; // move onto next poly


		// we will use the transformed polygon vertex list since the backface removal
		// only makes sense at the world coord stage further of the pipeline 

		// test the lighting mode of the polygon (use flat for flat, gouraud))
		if (curr_poly->attr & POLY4DV1_ATTR_SHADE_MODE_FLAT || curr_poly->attr & POLY4DV1_ATTR_SHADE_MODE_GOURAUD)
		{
			// step 1: extract the base color out in RGB mode
			if (m_pDxDrawEnv->m_dd_pixel_format == DD_PIXEL_FORMAT565)
			{
				_RGB565FROM16BIT(curr_poly->color, &r_base, &g_base, &b_base);

				// scale to 8 bit 
				r_base <<= 3;
				g_base <<= 2;
				b_base <<= 3;
			} // end if
			else
			{
				_RGB555FROM16BIT(curr_poly->color, &r_base, &g_base, &b_base);

				// scale to 8 bit 
				r_base <<= 3;
				g_base <<= 3;
				b_base <<= 3;
			} // end if

			// initialize color sum
			r_sum  = 0;
			g_sum  = 0;
			b_sum  = 0;

			// loop thru lights
			for (int curr_light = 0; curr_light < max_lights; curr_light++)
			{
				// is this light active
				if (m_lights[curr_light].state==LIGHTV1_STATE_OFF)
					continue;

				// what kind of light are we dealing with
				if (m_lights[curr_light].attr & LIGHTV1_ATTR_AMBIENT)
				{
					// simply multiply each channel against the color of the 
					// polygon then divide by 256 to scale back to 0..255
					// use a shift in real life!!! >> 8
					r_sum+= ((m_lights[curr_light].c_ambient.r * r_base) / 256);
					g_sum+= ((m_lights[curr_light].c_ambient.g * g_base) / 256);
					b_sum+= ((m_lights[curr_light].c_ambient.b * b_base) / 256);

					// there better only be one ambient light!

				} // end if
				else
					if (m_lights[curr_light].attr & LIGHTV1_ATTR_INFINITE)
					{
						// infinite lighting, we need the surface normal, and the direction
						// of the light source

						// we need to compute the normal of this polygon face, and recall
						// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
						CVector4d	n, l;

						nl = Calc_Plane_n(curr_poly->tvlist[ 0 ], curr_poly->tvlist[ 1 ], curr_poly->tvlist[ 2 ], n);

						// ok, recalling the lighting model for infinite lights
						// I(d)dir = I0dir * Cldir
						// and for the diffuse model
						// Itotald =   Rsdiffuse*Idiffuse * (n . l)
						// so we basically need to multiple it all together
						// notice the scaling by 128, I want to avoid floating point calculations, not because they 
						// are slower, but the conversion to and from cost cycles

						dp = n.Dot(m_lights[curr_light].dir);

						// only add light if dp > 0
						if (dp > 0)
						{ 
							i = 128*dp/nl; 
							r_sum+= (m_lights[curr_light].c_diffuse.r * r_base * i) / (256*128);
							g_sum+= (m_lights[curr_light].c_diffuse.g * g_base * i) / (256*128);
							b_sum+= (m_lights[curr_light].c_diffuse.b * b_base * i) / (256*128);
						} // end if

					} // end if infinite light
					else
						if (m_lights[curr_light].attr & LIGHTV1_ATTR_POINT)
						{
							// perform point light computations
							// light model for point light is once again:
							//              I0point * Clpoint
							//  I(d)point = ___________________
							//              kc +  kl*d + kq*d2              
							//
							//  Where d = |p - s|
							// thus it's almost identical to the infinite light, but attenuates as a function
							// of distance from the point source to the surface point being lit

							// we need to compute the normal of this polygon face, and recall
							// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
							CVector4d	n, l;

							nl = Calc_Plane_n(curr_poly->tvlist[ 0 ], curr_poly->tvlist[ 1 ], curr_poly->tvlist[ 2 ], n);

							// compute vector from surface to light
							l.Build(curr_poly->tvlist[0], m_lights[curr_light].pos);

							// compute distance and attenuation
							dist = l.Fast_Length(); 

							// and for the diffuse model
							// Itotald =   Rsdiffuse*Idiffuse * (n . l)
							// so we basically need to multiple it all together
							// notice the scaling by 128, I want to avoid floating point calculations, not because they 
							// are slower, but the conversion to and from cost cycles
							dp = n.Dot(l);

							// only add light if dp > 0
							if (dp > 0)
							{ 
								atten =  (m_lights[curr_light].kc + m_lights[curr_light].kl*dist + m_lights[curr_light].kq*dist*dist);    

								i = 128*dp / (nl * dist * atten ); 

								r_sum += (m_lights[curr_light].c_diffuse.r * r_base * i) / (256*128);
								g_sum += (m_lights[curr_light].c_diffuse.g * g_base * i) / (256*128);
								b_sum += (m_lights[curr_light].c_diffuse.b * b_base * i) / (256*128);
							} // end if

						} // end if point
						else
							if (m_lights[curr_light].attr & LIGHTV1_ATTR_SPOTLIGHT1)
							{
								// perform spotlight/point computations simplified model that uses
								// point light WITH a direction to simulate a spotlight
								// light model for point light is once again:
								//              I0point * Clpoint
								//  I(d)point = ___________________
								//              kc +  kl*d + kq*d2              
								//
								//  Where d = |p - s|
								// thus it's almost identical to the infinite light, but attenuates as a function
								// of distance from the point source to the surface point being lit

								// we need to compute the normal of this polygon face, and recall
								// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
								CVector4d	n, l;

								nl = Calc_Plane_n(curr_poly->tvlist[ 0 ], curr_poly->tvlist[ 1 ], curr_poly->tvlist[ 2 ], n);

								// compute vector from surface to light
								l.Build(curr_poly->tvlist[0], m_lights[curr_light].pos);

								// compute distance and attenuation
								dist = l.Fast_Length();

								// and for the diffuse model
								// Itotald =   Rsdiffuse*Idiffuse * (n . l)
								// so we basically need to multiple it all together
								// notice the scaling by 128, I want to avoid floating point calculations, not because they 
								// are slower, but the conversion to and from cost cycles

								// note that I use the direction of the light here rather than a the vector to the light
								// thus we are taking orientation into account which is similar to the spotlight model
								dp = n.Dot(m_lights[curr_light].dir);

								// only add light if dp > 0
								if (dp > 0)
								{ 
									atten =  (m_lights[curr_light].kc + m_lights[curr_light].kl*dist + m_lights[curr_light].kq*dist*dist);    

									i = 128*dp / (nl * atten ); 

									r_sum += (m_lights[curr_light].c_diffuse.r * r_base * i) / (256*128);
									g_sum += (m_lights[curr_light].c_diffuse.g * g_base * i) / (256*128);
									b_sum += (m_lights[curr_light].c_diffuse.b * b_base * i) / (256*128);
								} // end if

							} // end if spotlight1
							else
								if (m_lights[curr_light].attr & LIGHTV1_ATTR_SPOTLIGHT2) // simple version
								{
									// perform spot light computations
									// light model for spot light simple version is once again:
									//         	     I0spotlight * Clspotlight * MAX( (l . s), 0)^pf                     
									// I(d)spotlight = __________________________________________      
									//               		 kc + kl*d + kq*d2        
									// Where d = |p - s|, and pf = power factor

									// thus it's almost identical to the point, but has the extra term in the numerator
									// relating the angle between the light source and the point on the surface

									// we need to compute the normal of this polygon face, and recall
									// that the vertices are in cw order, u=p0->p1, v=p0->p2, n=uxv
									CVector4d	n, d, s;

									nl = Calc_Plane_n(curr_poly->tvlist[ 0 ], curr_poly->tvlist[ 1 ], curr_poly->tvlist[ 2 ], n);

									// and for the diffuse model
									// Itotald =   Rsdiffuse*Idiffuse * (n . l)
									// so we basically need to multiple it all together
									// notice the scaling by 128, I want to avoid floating point calculations, not because they 
									// are slower, but the conversion to and from cost cycles
									dp = n.Dot(m_lights[curr_light].dir);

									// only add light if dp > 0
									if (dp > 0)
									{ 
										// compute vector from light to surface (different from l which IS the light dir)
										s.Build( m_lights[curr_light].pos, curr_poly->tvlist[0]);

										// compute length of s (distance to light source) to normalize s for lighting calc
										dist = s.Fast_Length();  

										// compute spot light term (s . l)
										float dpsl = s.Dot(m_lights[curr_light].dir)/dist;

										// proceed only if term is positive
										if (dpsl > 0) 
										{
											// compute attenuation
											atten = (m_lights[curr_light].kc + m_lights[curr_light].kl*dist + m_lights[curr_light].kq*dist*dist);    

											// for speed reasons, pf exponents that are less that 1.0 are out of the question, and exponents
											// must be integral
											float dpsl_exp = dpsl;

											// exponentiate for positive integral powers
											for (int e_index = 1; e_index < (int)m_lights[curr_light].pf; e_index++)
												dpsl_exp*=dpsl;

											// now dpsl_exp holds (dpsl)^pf power which is of course (s . l)^pf 

											i = 128*dp * dpsl_exp / (nl * atten ); 

											r_sum += (m_lights[curr_light].c_diffuse.r * r_base * i) / (256*128);
											g_sum += (m_lights[curr_light].c_diffuse.g * g_base * i) / (256*128);
											b_sum += (m_lights[curr_light].c_diffuse.b * b_base * i) / (256*128);

										} // end if

									} // end if

								} // end if spot light

			} // end for light

			// make sure colors aren't out of range
			if (r_sum  > 255) r_sum = 255;
			if (g_sum  > 255) g_sum = 255;
			if (b_sum  > 255) b_sum = 255;

			// write the color over current color
			curr_poly->color = RGB16Bit(r_sum, g_sum, b_sum);

		} // end if
		else // assume POLY4DV1_ATTR_SHADE_MODE_CONSTANT
		{
			// emmisive shading only, do nothing
			// ...
		} // end if

	} // end for poly

	// return success
	return 0;
}